<?php

namespace common\libs\WeOpenDeveloper;

use yii\base\Exception;

class WechatPay
{
    private $mch_key;
    private $mch_id;
    private $appid;
    public $ispay = true;
    private $mch_cert = "";

    function __construct($account)
    {
        ini_set('date.timezone', 'Asia/Shanghai');

        if (!isset($account['appid']) || !isset($account['mch_id']) || !isset($account['mch_key'])) {
            $this->ispay = false;
            return;
        }

        if (empty($account['appid']) || empty($account['mch_id']) || empty($account['mch_key'])) {
            $this->ispay = false;
            return;
        }

        $this->appid = $account['appid'];
        $this->mch_id = $account['mch_id'];
        $this->mch_key = $account['mch_key'];
        $this->mch_cert = $account['mch_cert'];
    }

    function jspay($goods)
    {
        $result = array("flag" => false, "msg" => "", "data" => array());

        if (!$this->ispay) {
            $result['msg'] = "对不起，此公众号未开通微支付功能";
            return $result;
        }

        if (!isset($goods['openid']) || !isset($goods['body']) || !isset($goods['attach']) || !isset($goods['out_trade_no']) || !isset($goods['total_fee']) || !isset($goods['goods_tag'])) {
            $result['msg'] = "对不起，此公众号未开通微支付功能";
            return $result;
        }
        if (!isset($goods['notify_url'])) {
            $result['msg'] = "没有回调地址";
            return $result;
        }

        $unifiedorderRes = $this->unifiedorder($goods);
        $unifiedorderArr = $this->FromXml($unifiedorderRes);
        if (!isset($unifiedorderArr['prepay_id']) || !isset($unifiedorderArr['appid'])) {
            $unifiedorderArr['return_msg'] = $unifiedorderArr['return_code'] == 'SUCCESS' ? $unifiedorderArr['err_code_des'] : $unifiedorderArr['return_code'];
            $result['msg'] = isset($unifiedorderArr['return_msg']) ? $unifiedorderArr['return_msg'] : "统一下单失败";
            return $result;
        }

        if (empty($unifiedorderArr['prepay_id']) || empty($unifiedorderArr['appid'])) {
            $result['msg'] = "统一下单失败";
            return $result;
        }

        $jsParams = $this->getJSPayParams($unifiedorderArr['appid'], $unifiedorderArr['prepay_id']);

        $result['flag'] = true;
        $result['data'] = $jsParams;
        return $result;
    }

    /*
    *订单查询
    */
    function orderquery($order){
        $config = Yii::$app->params['wechat']['open'];
        $apiparams=[
            'appid'=> $this->appid,
            'mch_id'=> $this->mch_id,
            'out_trade_no'=>$order,
            'nonce_str'=>$this->getNonceStr()
        ];
        
        $arr = \Cm::$libs->wechat->OrderApi->query($apiparams);
        if(isset($arr['return_code']) && isset($arr['result_code'])){
            if($arr['return_code'] == 'SUCCESS' && $arr['result_code'] == 'SUCCESS' && $arr['trade_state'] == 'SUCCESS'){
                /*用戶订单支付成功*/
            }    
        }
        
        //echo "<pre>";print_r($arr);exit();
    }

    /*
    *订单退款
    */
    function orderrefund($goods){
        $result = array("flag" => false, "msg" => "", "data" => array());

        if (!$this->ispay) {
            $result['msg'] = "对不起，此公众号未开通微支付功能";
            return $result;
        }

        if (!isset($goods['out_refund_no']) || !isset($goods['out_trade_no']) || !isset($goods['total_fee']) || !isset($goods['refund_fee'])) {
            $result['msg'] = "对不起，此公众号未开通微支付功能";
            return $result;
        }
        if (!isset($goods['notify_url'])) {
            $result['msg'] = "没有回调地址";
            return $result;
        }
        if(empty($goods['total_fee']) || empty($goods['refund_fee'])){
            $result['msg'] = "退款金额不正确";
            return $result;
        }
        $data = array(
            'appid' => $this->appid,
            'mch_id' => $this->mch_id,
            'nonce_str'=>$this->getNonceStr(),
            'out_trade_no' => $goods['out_trade_no'],
            'out_refund_no' => $goods['out_refund_no'],
            'total_fee' => $goods['total_fee'],
            'refund_fee' => $goods['refund_fee'],
            'notify_url' => $goods['notify_url'],
        );
        $data['sign'] = $this->MakeSign($data);
        $xml = $this->ToXml($data);
        $res = $this->postXmlCurl($xml, "https://api.mch.weixin.qq.com/secapi/pay/refund",true);
        $this->log("refund_create", $data, $res);
        $refundres = $this->FromXml($res);
        if (!isset($refundres['refund_id']) || !isset($refundres['appid'])) {
            $refundres['return_msg'] = $refundres['return_code'] == 'SUCCESS' ? $refundres['err_code_des'] : $refundres['return_code'];
            $result['msg'] = isset($refundres['return_msg']) ? $refundres['return_msg'] : "生成退款单失败";
            return $result;
        }
        if (empty($refundres['refund_id']) || empty($refundres['appid'])) {
            $result['msg'] = "生成退款单失败";
            return $result;
        }
        $result['flag'] = true;
        $result['msg'] = $refundres['return_msg'];
        $result['data'] = $refundres;
        return $result;
    }

    private function getJSPayParams($appid, $prepay_id)
    {
        $time = time();
        $data = array(
            "appId" => $appid,
            "timeStamp" => (string)$time,
            "nonceStr" => $this->getNonceStr(),
            "package" => "prepay_id=" . $prepay_id,
            "signType" => 'MD5',
        );
        $data['paySign'] = $this->MakeSign($data);
        return $data;
    }

    private function unifiedorder($goods)
    {
        $data = array(
            'appid' => $this->appid,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->getNonceStr(),
            'body' => $goods['body'],
            'attach' => $goods['attach'],
            'out_trade_no' => $goods['out_trade_no'],
            'total_fee' => $goods['total_fee'],
            'spbill_create_ip' => '127.0.0.1',
            'time_start' => date("YmdHis"),
            'time_expire' => date("YmdHis", time() + 600),
            'goods_tag' => $goods['goods_tag'],
            'notify_url' => $goods['notify_url'],
            'trade_type' => 'JSAPI',
            'openid' => $goods['openid']
        );
        $data['sign'] = $this->MakeSign($data);
        $xml = $this->ToXml($data);
        $res = $this->postXmlCurl($xml, "https://api.mch.weixin.qq.com/pay/unifiedorder");
        $this->log("unifiedorder", $data, $res);
        return $res;
    }


    private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
    {
        try {
            $ch = curl_init();
            //设置超时
            curl_setopt($ch, CURLOPT_TIMEOUT, $second);


            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);//严格校验
            //设置header
            curl_setopt($ch, CURLOPT_HEADER, FALSE);
            //要求结果为字符串且输出到屏幕上
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

            if ($useCert) {
                if (!$this->checkCert())
                    return "";

                $params[CURLOPT_SSLCERTTYPE] = 'PEM';
                $params[CURLOPT_SSLCERT] = realpath(BASE_PATH . "/cert/apiclient_cert.pem");
                $params[CURLOPT_SSLKEYTYPE] = 'PEM';
                $params[CURLOPT_SSLKEY] = realpath(BASE_PATH . "/cert/apiclient_key.pem");
                curl_setopt_array($ch, $params);
            }

            //post提交方式
            curl_setopt($ch, CURLOPT_POST, TRUE);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
            //运行curl
            $data = curl_exec($ch);
            //echo $data;
            //返回结果
            if ($data) {
                curl_close($ch);
                return $data;
            } else {
                curl_close($ch);
                \Cm::$app->log->common("支付接口调用失败", [
                    'url' => $url,
                    'xml' => $xml,
                    'errorno' => curl_errno($ch)
                ]);
            }
        } catch (Exception $e) {

        }
    }

    private function FromXml($xml)
    {
        if (!$xml) {
            echo "xml数据异常！";
            exit;
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

        if (isset($arr['return_msg']))
            $arr['return_msg'] = $arr['return_msg'] == [] ? '' : $arr['return_msg'];

        return $arr;
    }

    private function ToXml($arr)
    {
        if (!is_array($arr)
            || count($arr) <= 0
        ) {
            echo "数组数据异常";
            exit;
        }

        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    private function getNonceStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    private function MakeSign($arr)
    {
        //签名步骤一：按字典序排序参数
        ksort($arr);
        $string = $this->ToUrlParams($arr);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=" . $this->mch_key;
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 格式化参数格式化成url参数
     */
    private function ToUrlParams($arr)
    {
        $buff = "";
        foreach ($arr as $k => $v) {
            if ($k != "sign" && $v != "" && !is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

    //检测证书
    private function checkCert()
    {
        $jsonCert = is_array($this->mch_cert) ? $this->mch_cert : json_decode($this->mch_cert, true);
        $apiclient_cert_p12 = isset($jsonCert['apiclient_cert.p12']) ? $jsonCert['apiclient_cert.p12'] : "";
        $apiclient_cert_pem = isset($jsonCert['apiclient_cert.pem']) ? $jsonCert['apiclient_cert.pem'] : "";
        $apiclient_key_pem = isset($jsonCert['apiclient_key.pem']) ? $jsonCert['apiclient_key.pem'] : "";

        $dir = BASE_PATH ."/cert";
        if(!is_dir($dir))
            mkdir($dir);

        $pathCert = BASE_PATH . "/cert/apiclient_cert.pem";
        $pathCertKey = BASE_PATH . "/cert/apiclient_key.pem";

        if (!file_exists($pathCert)) {
            if (empty($apiclient_cert_pem)) {
                \Cm::$app->log->common("未配置证书", "apiclient_cert.pem");
                return false;
            }

            $certfile = file_get_contents($apiclient_cert_pem);
            file_put_contents($pathCert, $certfile);

            if (!file_exists($pathCert))
                return false;
        }

        if (!file_exists($pathCertKey)) {
            if (empty($apiclient_key_pem)) {
                \Cm::$app->log->common("未配置证书", "apiclient_key.pem");
                return false;
            }

            $certfile = file_get_contents($apiclient_key_pem);
            file_put_contents($pathCertKey, $certfile);

            if (!file_exists($pathCertKey))
                return false;
        }
        return true;
    }

    public function log($type, $reqData, $resData)
    {
        $data = [
            "req" => $reqData,
            'res' => $resData
        ];
        \Cm::$app->log->common($type, $data);
    }
}